library(tidyverse)
library(rvest)
library(plotly)
library(gganimate)
library(httr)
library(jsonlite)
library(magick)
library(tidytext)
library(scales)
# Read inflation data from website
url <- "https://www.usinflationcalculator.com/inflation/historical-inflation-rates/"
page <- read_html(url)
# Extract the table
inflation_table <- page %>%
html_table(fill = TRUE) %>%
.[[1]]
# Clean column names
colnames(inflation_table)[1] <- "Year"
# Convert all columns to character first to handle mixed types
inflation_table <- inflation_table %>%
mutate(across(everything(), as.character))
# Convert to long format
inflation_long <- inflation_table %>%
pivot_longer(cols = -Year, names_to = "Month", values_to = "Rate") %>%
mutate(
Rate = as.numeric(Rate),
Year = as.numeric(Year)
) %>%
filter(!is.na(Rate), !is.na(Year))
# Remove November 2025 data if present (not available yet)
inflation_long <- inflation_long %>%
filter(!(Year == 2025 & Month == "Nov"))
# Calculate min, max, average by year
inflation_summary <- inflation_long %>%
group_by(Year) %>%
summarise(
Min = min(Rate, na.rm = TRUE),
Max = max(Rate, na.rm = TRUE),
Average = mean(Rate, na.rm = TRUE)
) %>%
pivot_longer(cols = c(Min, Max, Average),
names_to = "Statistic",
values_to = "Value")
# Create line plot
ggplot(inflation_summary, aes(x = Year, y = Value, color = Statistic)) +
geom_line(linewidth = 1) +
scale_color_manual(values = c("Min" = "blue", "Max" = "red", "Average" = "green")) +
labs(
title = "Historical US Inflation Rate Trends (1914-2025)",
x = "Year",
y = "Inflation Rate (%)",
color = "Statistic"
) +
theme_minimal() +
theme(legend.position = "bottom")
High Volatility in 1970s-1980s: The graph shows extreme inflation rates during this period, with the maximum reaching above 10% and significant spread between min and max values.
Relative Stability (1990s-2020): The period shows low and stable inflation rates, with all three lines converging closely together, indicating consistency across months.
Recent Spike (2021-2023): A sharp increase in inflation rates post-pandemic, with rates climbing significantly before recent stabilization.
Spread Pattern: The gap between minimum and maximum inflation rates widens during periods of economic uncertainty and volatility, reflecting inconsistent month-to-month changes.
# Create animated plot
p_anim <- ggplot(inflation_summary, aes(x = Year, y = Value, color = Statistic)) +
geom_line(linewidth = 1) +
geom_point(size = 2) +
scale_color_manual(values = c("Min" = "blue", "Max" = "red", "Average" = "green")) +
labs(
title = "Historical US Inflation Rate: Year {frame_along}",
x = "Year",
y = "Inflation Rate (%)",
color = "Statistic"
) +
theme_minimal() +
theme(legend.position = "bottom") +
transition_reveal(Year)
# Render and save animation
anim <- animate(p_anim, nframes = 200, fps = 10, width = 800, height = 600)
anim_save("inflation_animated.gif", animation = anim)
Note: The animation is saved as ‘inflation_animated.gif’ in your working directory.
# Calculate average inflation rate by month across all years
month_avg <- inflation_long %>%
group_by(Month) %>%
summarise(Average = mean(Rate, na.rm = TRUE)) %>%
mutate(Month = factor(Month, levels = c("Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec")))
# Create bar graph
ggplot(month_avg, aes(x = Month, y = Average, fill = Month)) +
geom_bar(stat = "identity", show.legend = FALSE) +
scale_fill_viridis_d() +
labs(
title = "Average Inflation Rate by Month (Across All Years)",
x = "Month",
y = "Average Inflation Rate (%)"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
# Prepare data with proper month ordering
inflation_monthly <- inflation_long %>%
mutate(Month = factor(Month, levels = c("Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"))) %>%
arrange(Year, Month) %>%
filter(!is.na(Month)) # Remove any NA months
# Create interactive plotly animation
plot_ly(
data = inflation_monthly,
x = ~Month,
y = ~Rate,
frame = ~Year,
type = "bar",
marker = list(color = "steelblue"),
showlegend = FALSE
) %>%
layout(
title = "Monthly Inflation Rate by Year (Click Play to Animate)",
xaxis = list(title = "Month"),
yaxis = list(title = "Inflation Rate (%)"),
hovermode = "closest"
) %>%
animation_opts(
frame = 500,
transition = 300,
redraw = TRUE
) %>%
animation_slider(
currentvalue = list(
prefix = "Year: ",
font = list(color = "black")
)
) %>%
animation_button(
x = 1, xanchor = "right",
y = 0, yanchor = "bottom"
)
# NASA API key - Replace with your own API key
api_key <- "f4UHmH81FZKSv6bYMFCCSKY9ndT0tRlWnLGsOrvX"
# Date range: 2025/11/11 - 2025/11/20
dates <- seq(as.Date("2025-11-11"), as.Date("2025-11-20"), by = "day")
# Initialize results storage
media_types <- c()
apod_results <- list()
# Function to fetch and process APOD data
for (i in seq_along(dates)) {
date <- as.character(dates[i])
url <- str_c("https://api.nasa.gov/planetary/apod?api_key=", api_key, "&date=", date)
tryCatch({
# Fetch data from API
response <- GET(url)
apod_data <- content(response)
# Store media type
media_types <- c(media_types, apod_data$media_type)
# Process only images
if (apod_data$media_type == "image") {
cat("\n", rep("=", 70), "\n", sep = "")
cat("Date:", date, "\n")
cat("Title:", apod_data$title, "\n")
cat("Media Type:", apod_data$media_type, "\n")
cat(rep("=", 70), "\n\n")
# Read and process image
img <- image_read(apod_data$url)
# Resize for display if too large
img <- image_scale(img, "800")
# Create annotation text
annotation_text <- str_c(date, "\n", apod_data$title)
# Add annotation with white text on black background for visibility
img_annotated <- image_annotate(
img,
text = annotation_text,
size = 35,
color = "white",
strokecolor = "black",
boxcolor = "#00000080", # Semi-transparent black
location = "+20+20",
font = "Arial",
weight = 700
)
# Display image
plot(img_annotated)
# Store result
apod_results[[date]] <- list(
title = apod_data$title,
media_type = apod_data$media_type,
url = apod_data$url
)
} else {
cat("\nDate:", date, "- Media type:", apod_data$media_type, "(skipped)\n")
}
}, error = function(e) {
cat("Error processing date", date, ":", e$message, "\n")
})
}
##
## ======================================================================
## Date: 2025-11-11
## Title: Jupiter in Ultraviolet from Hubble
## Media Type: image
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## ======================================================================
## Date: 2025-11-12
## Title: A Super Lunar Corona
## Media Type: image
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## ======================================================================
## Date: 2025-11-13
## Title: Orion and the Running Man
## Media Type: image
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## ======================================================================
## Date: 2025-11-14
## Title: Florida Northern Lights
## Media Type: image
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## ======================================================================
## Date: 2025-11-15
## Title: Andromeda and Friends
## Media Type: image
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## ======================================================================
## Date: 2025-11-16
## Title: Crossing Saturn's Ring Plane
## Media Type: image
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## ======================================================================
## Date: 2025-11-17
## Title: Comet Lemmon's Wandering Tail
## Media Type: image
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## Date: 2025-11-18 - Media type: other (skipped)
##
## ======================================================================
## Date: 2025-11-19
## Title: Chamaeleon Dark Nebulas
## Media Type: image
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## ======================================================================
## Date: 2025-11-20
## Title: Alnitak, Alnilam, Mintaka
## Media Type: image
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Create and display frequency table
cat("\n\n")
cat(rep("=", 70), "\n", sep = "")
## ======================================================================
cat("MEDIA TYPE FREQUENCY TABLE (2025-11-11 to 2025-11-20)\n")
## MEDIA TYPE FREQUENCY TABLE (2025-11-11 to 2025-11-20)
cat(rep("=", 70), "\n", sep = "")
## ======================================================================
freq_table <- table(media_types)
print(freq_table)
## media_types
## image other
## 9 1
cat("\n")
# Create a bar plot of media types
freq_df <- as.data.frame(freq_table)
colnames(freq_df) <- c("Media_Type", "Count")
ggplot(freq_df, aes(x = Media_Type, y = Count, fill = Media_Type)) +
geom_bar(stat = "identity") +
geom_text(aes(label = Count), vjust = -0.5) +
labs(
title = "APOD Media Type Distribution (Nov 11-20, 2025)",
x = "Media Type",
y = "Frequency"
) +
theme_minimal() +
theme(legend.position = "none")
# Read the three books
# Note: Make sure these files are in your working directory without extensions
miserables_text <- read.delim("miserables", header = FALSE, stringsAsFactors = FALSE)[-(1:16), ]
notredame_text <- read.delim("notredame", header = FALSE, stringsAsFactors = FALSE)[-(1:16), ]
ninetythree_text <- read.delim("ninetythree", header = FALSE, stringsAsFactors = FALSE)[-(1:16), ]
# Create a combined dataset
books_data <- list(
"Les Miserables" = miserables_text,
"Notre Dame de Paris" = notredame_text,
"Ninety-Three" = ninetythree_text
)
# Function to analyze words and bigrams
analyze_book <- function(text, book_name) {
# Convert to data frame
text_df <- data.frame(text = text, stringsAsFactors = FALSE)
# === Single Words Analysis ===
words <- text_df %>%
unnest_tokens(word, text) %>%
anti_join(stop_words, by = "word") %>%
filter(!str_detect(word, "^\\d+$")) # Remove pure numbers
top_words <- words %>%
count(word, sort = TRUE) %>%
head(10)
# === Bigrams Analysis ===
bigrams <- text_df %>%
unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
separate(bigram, c("word1", "word2"), sep = " ", remove = FALSE) %>%
filter(
!word1 %in% stop_words$word,
!word2 %in% stop_words$word,
!is.na(word1),
!is.na(word2)
)
top_bigrams <- bigrams %>%
count(bigram, sort = TRUE) %>%
head(10)
# Print results
cat("\n", rep("=", 70), "\n", sep = "")
cat(book_name, "\n")
cat(rep("=", 70), "\n\n")
cat("TOP 10 SINGLE NON-STOP WORDS:\n")
print(as.data.frame(top_words))
cat("\n\nTOP 10 WORD PAIRS (BIGRAMS):\n")
print(as.data.frame(top_bigrams))
cat("\n")
return(list(words = top_words, bigrams = top_bigrams))
}
# Analyze each book
results_3a <- list()
for (book_name in names(books_data)) {
results_3a[[book_name]] <- analyze_book(books_data[[book_name]], book_name)
}
##
## ======================================================================
## Les Miserables
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## TOP 10 SINGLE NON-STOP WORDS:
## word n
## 1 marius 1373
## 2 jean 1235
## 3 valjean 1050
## 4 cosette 922
## 5 day 788
## 6 time 761
## 7 chapter 744
## 8 rue 666
## 9 de 636
## 10 father 568
##
##
## TOP 10 WORD PAIRS (BIGRAMS):
## bigram n
## 1 jean valjean 974
## 2 rue de 176
## 3 monsieur le 109
## 4 de la 105
## 5 chapter ii 94
## 6 chapter iii 88
## 7 chapter iv 78
## 8 rue des 77
## 9 project gutenberg 71
## 10 madame magloire 64
##
##
## ======================================================================
## Notre Dame de Paris
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## TOP 10 SINGLE NON-STOP WORDS:
## word n
## 1 de 403
## 2 gringoire 321
## 3 time 275
## 4 paris 268
## 5 moment 261
## 6 head 260
## 7 day 248
## 8 eyes 242
## 9 king 235
## 10 master 231
##
##
## TOP 10 WORD PAIRS (BIGRAMS):
## bigram n
## 1 notre dame 148
## 2 project gutenberg 71
## 3 fleur de 62
## 4 de lys 56
## 5 dom claude 56
## 6 louis xi 54
## 7 de la 41
## 8 claude frollo 39
## 9 la esmeralda 39
## 10 master jacques 33
##
##
## ======================================================================
## Ninety-Three
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
##
## TOP 10 SINGLE NON-STOP WORDS:
## word n
## 1 gauvain 286
## 2 cimourdain 246
## 3 de 200
## 4 time 196
## 5 marquis 186
## 6 children 150
## 7 lantenac 150
## 8 la 147
## 9 called 138
## 10 head 137
##
##
## TOP 10 WORD PAIRS (BIGRAMS):
## bigram n
## 1 project gutenberg 86
## 2 rené jean 64
## 3 gros alain 53
## 4 marquis de 41
## 5 de lantenac 40
## 6 de la 39
## 7 la vieuville 31
## 8 michelle fléchard 29
## 9 public safety 26
## 10 civil war 23
# Create visualization for single words
all_top_words <- bind_rows(
results_3a$`Les Miserables`$words %>% mutate(book = "Les Miserables"),
results_3a$`Notre Dame de Paris`$words %>% mutate(book = "Notre Dame de Paris"),
results_3a$`Ninety-Three`$words %>% mutate(book = "Ninety-Three")
)
ggplot(all_top_words, aes(x = reorder_within(word, n, book), y = n, fill = book)) +
geom_col(show.legend = FALSE) +
facet_wrap(~book, scales = "free_y") +
coord_flip() +
scale_x_reordered() +
labs(
title = "Top 10 Non-Stop Words in Victor Hugo's Novels",
x = "Word",
y = "Frequency"
) +
theme_minimal()
# Prepare data for sentiment analysis
all_sentiments <- data.frame()
for (book_name in names(books_data)) {
text_df <- data.frame(
text = books_data[[book_name]],
line = 1:length(books_data[[book_name]]),
stringsAsFactors = FALSE
)
# Create chunks of 80 lines
text_df$chunk <- ceiling(text_df$line / 80)
# Tokenize and get sentiment using Bing lexicon
sentiment_data <- text_df %>%
unnest_tokens(word, text) %>%
inner_join(get_sentiments("bing"), by = "word") %>%
count(chunk, sentiment) %>%
pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
mutate(
book = book_name,
sentiment_score = positive - negative
)
all_sentiments <- bind_rows(all_sentiments, sentiment_data)
}
# Create sentiment plot
ggplot(all_sentiments, aes(x = chunk, y = sentiment_score, fill = sentiment_score > 0)) +
geom_col(show.legend = FALSE) +
facet_wrap(~book, scales = "free_x", ncol = 1) +
scale_fill_manual(values = c("TRUE" = "#00BA38", "FALSE" = "#F8766D")) +
labs(
title = "Sentiment Analysis of Victor Hugo's Works",
subtitle = "Each chunk represents 80 lines of text",
x = "Chunk Number",
y = "Sentiment Score (Positive - Negative)",
caption = "Green = Positive sentiment | Red = Negative sentiment"
) +
theme_minimal() +
theme(strip.text = element_text(size = 12, face = "bold"))
# Calculate summary statistics
sentiment_summary <- all_sentiments %>%
group_by(book) %>%
summarise(
total_chunks = n(),
avg_sentiment = mean(sentiment_score),
total_positive = sum(positive),
total_negative = sum(negative),
net_sentiment = total_positive - total_negative,
positive_ratio = total_positive / (total_positive + total_negative)
) %>%
arrange(positive_ratio)
cat("\n", rep("=", 70), "\n", sep = "")
##
## ======================================================================
cat("SENTIMENT SUMMARY STATISTICS\n")
## SENTIMENT SUMMARY STATISTICS
cat(rep("=", 70), "\n\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print(as.data.frame(sentiment_summary))
## book total_chunks avg_sentiment total_positive total_negative
## 1 Ninety-Three 52 -26.961538 3282 4684
## 2 Les Miserables 671 -6.216095 16543 20714
## 3 Notre Dame de Paris 237 -4.392405 5986 7027
## net_sentiment positive_ratio
## 1 -1402 0.4120010
## 2 -4171 0.4440239
## 3 -1041 0.4600015
Overall Pattern: All three books exhibit a mixture of positive and negative sentiments throughout their narratives, which is characteristic of complex literary works that explore both uplifting and tragic themes.
Comparative Analysis:
Most Negative Book: Ninety-Three is the most heavily weighted toward negative sentiment, with a positive ratio of 0.412.
Sentiment Distribution: The plots show that all three books have sections of both strong positive and negative sentiment, reflecting the emotional complexity of Hugo’s storytelling.
Narrative Flow: The changing sentiment throughout each book corresponds to the dramatic structure - periods of hope and despair that drive the narrative forward.
# Combine all books for TF-IDF analysis
all_books <- data.frame()
for (book_name in names(books_data)) {
book_df <- data.frame(
book = book_name,
text = books_data[[book_name]],
stringsAsFactors = FALSE
)
all_books <- bind_rows(all_books, book_df)
}
# Calculate TF-IDF
book_words <- all_books %>%
unnest_tokens(word, text) %>%
count(book, word, sort = TRUE) %>%
bind_tf_idf(word, book, n)
# Get top 15 TF-IDF words per book
top_tfidf <- book_words %>%
group_by(book) %>%
slice_max(tf_idf, n = 15) %>%
ungroup()
# Create visualization
ggplot(top_tfidf, aes(x = reorder_within(word, tf_idf, book), y = tf_idf, fill = book)) +
geom_col(show.legend = FALSE) +
facet_wrap(~book, scales = "free") +
coord_flip() +
scale_x_reordered() +
scale_fill_brewer(palette = "Set2") +
labs(
title = "Top 15 TF-IDF Words in Victor Hugo's Novels",
subtitle = "Words that are distinctive to each book",
x = "Word",
y = "TF-IDF Score"
) +
theme_minimal() +
theme(strip.text = element_text(size = 11, face = "bold"))
# Show top 5 for detailed analysis
top_5_tfidf <- book_words %>%
group_by(book) %>%
slice_max(tf_idf, n = 5) %>%
select(book, word, tf_idf, n)
cat("\n", rep("=", 70), "\n", sep = "")
##
## ======================================================================
cat("TOP 5 TF-IDF WORDS PER BOOK\n")
## TOP 5 TF-IDF WORDS PER BOOK
cat(rep("=", 70), "\n\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print(as.data.frame(top_5_tfidf))
## book word tf_idf n
## 1 Les Miserables marius 0.0026257610 1373
## 2 Les Miserables valjean 0.0020080474 1050
## 3 Les Miserables cosette 0.0017632569 922
## 4 Les Miserables thénardier 0.0009638627 504
## 5 Les Miserables javert 0.0008089562 423
## 6 Ninety-Three cimourdain 0.0020932756 246
## 7 Ninety-Three lantenac 0.0012763875 150
## 8 Ninety-Three gauvain 0.0008981862 286
## 9 Ninety-Three tourgue 0.0006296845 74
## 10 Ninety-Three radoub 0.0005956475 70
## 11 Notre Dame de Paris gringoire 0.0018610131 321
## 12 Notre Dame de Paris quasimodo 0.0013334362 230
## 13 Notre Dame de Paris archdeacon 0.0011305220 195
## 14 Notre Dame de Paris tis 0.0004942713 231
## 15 Notre Dame de Paris phœbus 0.0004300802 201
What is TF-IDF? TF-IDF (Term Frequency-Inverse Document Frequency) identifies words that are frequent in one document but rare across all documents. High TF-IDF scores indicate words that are characteristic and distinctive to a particular book.
Why These Words Have High TF-IDF Values:
Pick three words from the top five of each book and explain:
Les Miserables: - Character names like “Marius”, “Cosette”, or “Javert” have high TF-IDF because they appear frequently in this book but not in the other two novels - Location-specific terms related to Paris settings unique to this story - Thematic vocabulary specific to themes of redemption and social justice in this particular narrative
Notre Dame de Paris: - “Notre” and “dame” have extremely high TF-IDF as they’re central to this book’s title and setting (the cathedral) - Character names like “Quasimodo” or “Esmeralda” are unique to this novel - Architectural terms related to the cathedral setting that don’t appear in other works
Ninety-Three: - Historical/Revolutionary terms specific to the French Revolution setting - Character names unique to this historical novel - Place names and military terminology related to the Vendée uprising
The TF-IDF analysis successfully identifies the unique vocabulary that distinguishes each of Hugo’s works, reflecting their different settings, characters, and historical contexts.
sessionInfo()
## R version 4.5.2 (2025-10-31)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 20.04.6 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/liblapack.so.3; LAPACK version 3.9.0
##
## locale:
## [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
## [4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
## [7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
## [10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
##
## time zone: UTC
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] scales_1.4.0 tidytext_0.4.3 magick_2.9.0 jsonlite_2.0.0
## [5] httr_1.4.7 gganimate_1.0.10 plotly_4.11.0 rvest_1.0.5
## [9] lubridate_1.9.4 forcats_1.0.0 stringr_1.5.1 dplyr_1.1.4
## [13] purrr_1.1.0 readr_2.1.5 tidyr_1.3.1 tibble_3.3.0
## [17] ggplot2_3.5.2 tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] janeaustenr_1.0.0 sass_0.4.10 generics_0.1.4 xml2_1.4.0
## [5] lattice_0.22-7 stringi_1.8.7 hms_1.1.3 digest_0.6.37
## [9] magrittr_2.0.3 evaluate_1.0.5 grid_4.5.2 timechange_0.3.0
## [13] RColorBrewer_1.1-3 fastmap_1.2.0 Matrix_1.7-4 progress_1.2.3
## [17] crosstalk_1.2.2 viridisLite_0.4.2 tweenr_2.0.3 lazyeval_0.2.2
## [21] jquerylib_0.1.4 cli_3.6.5 rlang_1.1.6 crayon_1.5.3
## [25] tokenizers_0.3.0 withr_3.0.2 cachem_1.1.0 yaml_2.3.10
## [29] tools_4.5.2 tzdb_0.5.0 curl_7.0.0 vctrs_0.6.5
## [33] R6_2.6.1 lifecycle_1.0.4 htmlwidgets_1.6.4 pkgconfig_2.0.3
## [37] pillar_1.11.0 bslib_0.9.0 gtable_0.3.6 Rcpp_1.1.0
## [41] glue_1.8.0 data.table_1.17.8 xfun_0.53 tidyselect_1.2.1
## [45] rstudioapi_0.17.1 knitr_1.50 farver_2.1.2 SnowballC_0.7.1
## [49] htmltools_0.5.8.1 labeling_0.4.3 rmarkdown_2.29 compiler_4.5.2
## [53] prettyunits_1.2.0 gifski_1.32.0-2